# package deb

# import (
#     "bytes"
#     "errors"
#     "fmt"
#     "log"
#     "sort"
#     "strings"
#     "sync"
#     "time"

#     "github.com/aptly-dev/aptly/database"
#     "github.com/aptly-dev/aptly/utils"
#     "github.com/pborman/uuid"
#     "github.com/ugorji/go/codec"
# )

# // Snapshot is immutable state of repository: list of packages
from deb.reflist import *
class Snapshot :
    # // Persisten internal ID
    UUID :str =''
    # // Human-readable name
    Name :str=''
    # // Date of creation
    CreatedAt =None#time.Time

    # // Source: kind + ID
    SourceKind :str  =''
    SourceIDs  :List[str] =[]
    # // Description of how snapshot was created
    Description :str=''

    Origin               :str=''
    NotAutomatic         :str=''
    ButAutomaticUpgrades :str=''

    packageRefs :PackageRefList=None

    # // String returns string representation of snapshot
    def   String(s) ->str :
        return  "[{}]: {}".format( s.Name, s.Description)
    

    # // NumPackages returns number of packages in snapshot
    def  NumPackages(s) ->int :
        return s.packageRefs.Len()
    

    # // RefList returns list of package refs in snapshot
    def  RefList(s) ->PackageRefList :
        return s.packageRefs
    

    # // Key is a unique id in DB
    def   Key(s) ->str:
        return  "S" + s.UUID
    

    # // RefKey is a unique id for package reference list
    def   RefKey(s) ->str :
        return  "E" + s.UUID
    

    # // Encode does msgpack encoding of Snapshot
    # def (s *Snapshot) Encode() []byte {
    #     var buf bytes.Buffer

    #     encoder = codec.NewEncoder(&buf, &codec.MsgpackHandle{})
    #     encoder.Encode(s)

    #     return buf.Bytes()
    # }

    # // Decode decodes msgpack representation into Snapshot
    # def (s *Snapshot) Decode(input []byte) error {
    #     decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
    #     err = decoder.Decode(s)
    #     if err is not None  {
    #         if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") {
    #             // probably it is broken DB from go < 1.2, try decoding w/o time.Time
    #             var snapshot11 struct {
    #                 UUID      string
    #                 Name      string
    #                 CreatedAt []byte

    #                 SourceKind  string
    #                 SourceIDs   []string
    #                 Description string
    #             }

    #             decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
    #             err2 = decoder.Decode(&snapshot11)
    #             if err2 is not None  {
    #                 return err
    #             }

    #             s.UUID = snapshot11.UUID
    #             s.Name = snapshot11.Name
    #             s.SourceKind = snapshot11.SourceKind
    #             s.SourceIDs = snapshot11.SourceIDs
    #             s.Description = snapshot11.Description
    #         } else if strings.Contains(err.Error(), "invalid length of bytes for decoding time") {
    #             // DB created by old codec version, time.Time is not builtin type.
    #             // https://github.com/ugorji/go-codec/issues/269
    #             decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{
    #                 // only can be configured in Deprecated BasicHandle struct
    #                 BasicHandle: codec.BasicHandle{ // nolint: staticcheck
    #                     TimeNotBuiltin: True,
    #                 },
    #             })
    #             if err = decoder.Decode(s); err is not None  {
    #                 return err
    #             }
    #         } else {
    #             return err
    #         }
    #     }
    #     return None
    # }
from deb.remote import *
import time
# // NewSnapshotFromRepository creates snapshot from current state of repository
def NewSnapshotFromRepository(name :str, repo :RemoteRepo)->Tuple [Snapshot, str] :
    if repo.packageRefs is None  :
        return None,  "mirror not updated"
    

    snapshot= Snapshot()
    snapshot.UUID=                 str(uuid.uuid4())
    snapshot.Name=                 name
    snapshot.CreatedAt=            time.time()
    snapshot.SourceKind=           SourceRemoteRepo
    snapshot.SourceIDs=            [repo.UUID]
    snapshot.Description=          "Snapshot from mirror {}".format(repo)
    snapshot.Origin=               repo.Meta["Origin"]
    snapshot.NotAutomatic=         repo.Meta["NotAutomatic"]
    snapshot.ButAutomaticUpgrades= repo.Meta["ButAutomaticUpgrades"]
    snapshot.packageRefs=          repo.packageRefs
    
    return snapshot,None

from deb.local import *
# // NewSnapshotFromLocalRepo creates snapshot from current state of local repository
def NewSnapshotFromLocalRepo(name :str, repo :LocalRepo) ->Tuple[Snapshot, str] :
    snap = Snapshot()
    snap.UUID= str(uuid.uuid4())
    snap.Name= name
    snap.CreatedAt= time.time()
    snap.SourceKind=  SourceLocalRepo
    snap.SourceIDs=   [repo.UUID]
    snap.Description=  "Snapshot from local repo {}".format(repo)
    snap.packageRefs= repo.packageRefs
    

    if snap.packageRefs is None  :
        snap.packageRefs = NewPackageRefList()
    

    return snap, None

# // NewSnapshotFromPackageList creates snapshot from PackageList
def NewSnapshotFromPackageList(name :str, sources :List[Snapshot], lists :PackageRefList, description :str) ->Snapshot :
    return NewSnapshotFromRefList(name, sources, NewPackageRefListFromPackageList(lists), description)


# // NewSnapshotFromRefList creates snapshot from PackageRefList
def NewSnapshotFromRefList(name :str, sources :List[Snapshot], lists :PackageRefList, description :str) ->Snapshot :
    sourceUUIDs =[]# make([]string, len(sources))
    for i in sources :
        sourceUUIDs.append(sources[i].UUID)
    
    snapshot= Snapshot()
     
    snapshot.UUID= str(uuid.uuid4())
    snapshot.Name= name
    snapshot.CreatedAt= time.time()
    snapshot.SourceKind="snapshot",
    snapshot.SourceIDs=   sourceUUIDs
    snapshot.Description= description
    snapshot.packageRefs= lists
    return snapshot

from typing import List,Dict,Tuple
# // SnapshotCollection does listing, updating/adding/deleting of Snapshots
class SnapshotCollection :
#     *sync.RWMutex
    db    :database.Storage=None
    cache :dict={}#map[string]*Snapshot
# }



    # // Add appends new repo to collection and saves it
    def  Add(collection,snapshot :Snapshot) ->str:
        _, err = collection.ByName(snapshot.Name)
        if err is None  :
            return  "snapshot with name {} already exists".format(snapshot.Name)
        

        err = collection.Update(snapshot)
        if err is not None  :
            return err
        

        collection.cache[snapshot.UUID] = snapshot
        return None
    

    # // Update stores updated information about snapshot in DB
    # def (collection *SnapshotCollection) Update(snapshot *Snapshot) error {
    #     transaction, err = collection.db.OpenTransaction()
    #     if err is not None  {
    #         return err
    #     }
    #     defer transaction.Discard()

    #     err = transaction.Put(snapshot.Key(), snapshot.Encode())
    #     if err is not None  {
    #         return err
    #     }
    #     if snapshot.packageRefs is not None  {
    #         if err = transaction.Put(snapshot.RefKey(), snapshot.packageRefs.Encode()); err is not None  {
    #             return err
    #         }
    #     }
    #     return transaction.Commit()
    # }

    # // LoadComplete loads additional information about snapshot
    # def (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error {
    #     encoded, err = collection.db.Get(snapshot.RefKey())
    #     if err is not None  {
    #         return err
    #     }

    #     snapshot.packageRefs = &PackageRefList{}
    #     return snapshot.packageRefs.Decode(encoded)
    # }

    # def (collection *SnapshotCollection) search(filter def(*Snapshot) bool, unique bool) []*Snapshot {
    #     result = []*Snapshot(None)
    #     for _, s = range collection.cache {
    #         if filter(s) {
    #             result = append(result, s)
    #         }
    #     }

    #     if unique and len(result) > 0 {
    #         return result
    #     }

    #     collection.db.ProcessByPrefix([]byte("S"), def(key, blob []byte) error {
    #         s = &Snapshot{}
    #         if err = s.Decode(blob); err is not None  {
    #             log.Printf("Error decoding snapshot: %s\n", err)
    #             return None
    #         }

    #         if filter(s) {
    #             if _, exists = collection.cache[s.UUID]; !exists {
    #                 collection.cache[s.UUID] = s
    #                 result = append(result, s)
    #                 if unique {
    #                     return errors.New("abort")
    #                 }
    #             }
    #         }

    #         return None
    #     })

    #     return result
    # }

    # // ByName looks up snapshot by name
    def ByName(collection,name :str) ->Tuple[Snapshot, str] :
        def NoName(s:Snapshot)->bool:
            return s.Name == name
        result = collection.search(NoName, True)
        if len(result) > 0 :
            return result[0], None
        

        return None, "snapshot with name {} not found".format(name)
    

    # // ByUUID looks up snapshot by UUID
    # def (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
    #     if s, ok = collection.cache[uuid]; ok {
    #         return s, None
    #     }

    #     key = (&Snapshot{UUID: uuid}).Key()

    #     value, err = collection.db.Get(key)
    #     if err == database.ErrNotFound {
    #         return None, fmt.Errorf("snapshot with uuid %s not found", uuid)
    #     }
    #     if err is not None  {
    #         return None, err
    #     }

    #     s = &Snapshot{}
    #     err = s.Decode(value)

    #     if err is None  {
    #         collection.cache[s.UUID] = s
    #     }

    #     return s, err
    # }

    # // ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
    # def (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
    #     return collection.search(def(s *Snapshot) bool {
    #         return s.SourceKind == SourceRemoteRepo and utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
    #     }, false)
    # }

    # // ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
    # def (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
    #     return collection.search(def(s *Snapshot) bool {
    #         return s.SourceKind == SourceLocalRepo and utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
    #     }, false)
    # }

    # // BySnapshotSource looks up snapshots that have specified snapshot as a source
    # def (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
    #     return collection.search(def(s *Snapshot) bool {
    #         return s.SourceKind == "snapshot" and utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID)
    #     }, false)
    # }

    # // ForEach runs method for each snapshot
    # def (collection *SnapshotCollection) ForEach(handler def(*Snapshot) error) error {
    #     return collection.db.ProcessByPrefix([]byte("S"), def(key, blob []byte) error {
    #         s = &Snapshot{}
    #         if err = s.Decode(blob); err is not None  {
    #             log.Printf("Error decoding snapshot: %s\n", err)
    #             return None
    #         }

    #         return handler(s)
    #     })
    # }

    # // ForEachSorted runs method for each snapshot following some sort order
    # def (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler def(*Snapshot) error) error {
    #     blobs = collection.db.FetchByPrefix([]byte("S"))
    #     list = make([]*Snapshot, 0, len(blobs))

    #     for _, blob = range blobs {
    #         s = &Snapshot{}
    #         if err = s.Decode(blob); err is not None  {
    #             log.Printf("Error decoding snapshot: %s\n", err)
    #         } else {
    #             list = append(list, s)
    #         }
    #     }

    #     sorter, err = newSnapshotSorter(sortMethod, list)
    #     if err is not None  {
    #         return err
    #     }

    #     for _, s = range sorter.list {
    #         err = handler(s)
    #         if err is not None  {
    #             return err
    #         }
    #     }

    #     return None
    # }

    # // Len returns number of snapshots in collection
    # // ForEach runs method for each snapshot
    # def (collection *SnapshotCollection) Len() int {
    #     return len(collection.db.KeysByPrefix([]byte("S")))
    # }

    # // Drop removes snapshot from collection
    # def (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
    #     transaction, err = collection.db.OpenTransaction()
    #     if err is not None  {
    #         return err
    #     }
    #     defer transaction.Discard()

    #     if _, err = transaction.Get(snapshot.Key()); err is not None  {
    #         if err == database.ErrNotFound {
    #             return errors.New("snapshot not found")
    #         }

    #         return err
    #     }

    #     delete(collection.cache, snapshot.UUID)

    #     if err = transaction.Delete(snapshot.Key()); err is not None  {
    #         return err
    #     }

    #     if err = transaction.Delete(snapshot.RefKey()); err is not None  {
    #         return err
    #     }

    #     return transaction.Commit()
    # }




# // NewSnapshotCollection loads Snapshots from DB and makes up collection
def NewSnapshotCollection(db :database.Storage) ->SnapshotCollection :
    snapshotCollection=SnapshotCollection()
    snapshotCollection.db=db
    snapshotCollection.cache={}
    return snapshotCollection
from enum import Enum
# // Snapshot sorting methods
class sortMethods(Enum):
    SortName = 0
    SortTime=1
# )

class snapshotSorter :
    list       =[]#*Snapshot
    sortMethod :int=None

    # def (s *snapshotSorter) Swap(i, j int) {
    #     s.list[i], s.list[j] = s.list[j], s.list[i]
    # }

    # def (s *snapshotSorter) Less(i, j int) bool {
    #     switch s.sortMethod {
    #     case SortName:
    #         return s.list[i].Name < s.list[j].Name
    #     case SortTime:
    #         return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
    #     }
    #     raise ("unknown sort method")
    # }

    # def (s *snapshotSorter) Len() int {
    #     return len(s.list)
    # }


def newSnapshotSorter(sortMethod :str, snapshotLists :List[Snapshot]) ->Tuple[snapshotSorter, str] :
    s = snapshotSorter()#&snapshotSorter{list: list}
    s.list=snapshotLists
    
    if sortMethod in ["time", "Time"]:
        s.sortMethod = sortMethods.SortTime
    elif sortMethod in  ["name", "Name"]:
        s.sortMethod = sortMethods.SortName
    else:
        return None, "sorting method \"{}\" unknown".format(sortMethod)
    

    # sort.Sort(s)

    return s, None


